package com.yide.myworld.intercepter;

import com.yide.myworld.annotation.RequestLimitWithIp;
import com.yide.myworld.annotation.RequestLimitWithToken;
import com.yide.myworld.constants.AuthConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>接口防刷的全局拦截器</p>
 */
@Component
@Slf4j
public class RequestLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        if (handler instanceof HandlerMethod) {
            //只拦截标注了@RequestLimitWithToken该注解
            HandlerMethod method = (HandlerMethod) handler;
            this.checkRequestLimitWithToken(method, request, currentTimeMillis);
            this.checkRequestLimitWithIp(method, request, currentTimeMillis);
        }
        return true;
    }


    /**
     * 通过token校验接口的请求频次限制
     *
     * @param method
     * @param request
     * @param currentTimeMillis
     */
    private void checkRequestLimitWithToken(HandlerMethod method, HttpServletRequest request, long currentTimeMillis) {
        //标注在方法上的@RequestLimitWithToken
        RequestLimitWithToken limitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RequestLimitWithToken.class);
        //标注在controler类上的@RequestLimitWithToken
        RequestLimitWithToken limitByClass = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RequestLimitWithToken.class);
        //没有限制接口防刷，直接跳过
        if (Objects.isNull(limitByMethod) && Objects.isNull(limitByClass)) {
            return;
        }
        // 方法上的注解优先级高于类的优先级
        RequestLimitWithToken limitAnno = Objects.nonNull(limitByMethod) ? limitByMethod : limitByClass;
        // 组合判断条件
        // 请求的URI
        String uri = request.getRequestURI();
        // 根据token获取用户id
        String userId = getUserId(request);
        String redisKey = getApiRequestLimitWithUserId(uri, userId);

        Boolean isExist = redisTemplate.hasKey(redisKey);
        if (Boolean.TRUE.equals(isExist)) {
            // 检验是否达到最大请求
            Long size = redisTemplate.opsForValue().increment(redisKey, 1);
            if (Objects.nonNull(size) && size > limitAnno.maxRequest()) {
                log.error("操作频繁，请稍后再试");
            }
        } else {
            redisTemplate.opsForValue().set(redisKey, 1, limitAnno.seconds(), TimeUnit.SECONDS);
        }
    }


    /**
     * 通过ip校验接口的请求频次限制
     *
     * @param method
     * @param request
     * @param currentTimeMillis
     */
    private void checkRequestLimitWithIp(HandlerMethod method, HttpServletRequest request, long currentTimeMillis) {
        //标注在方法上的@RequestLimitWithIp
        RequestLimitWithIp limitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RequestLimitWithIp.class);
        //标注在controler类上的@RequestLimitWithIp
        RequestLimitWithIp limitByClass = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RequestLimitWithIp.class);
        //没有限制接口防刷，直接跳过
        if (Objects.isNull(limitByMethod) && Objects.isNull(limitByClass)) {
            return;
        }
        // 方法上的注解优先级高于类的优先级
        RequestLimitWithIp limitAnno = Objects.nonNull(limitByMethod) ? limitByMethod : limitByClass;
        // 组合判断条件
        // 请求的URI
        String uri = request.getRequestURI();
        // 请求端的地址ip
        String ip = request.getRemoteAddr();
        String redisKey = getApiRequestLimitWithIp(uri, ip);

        Boolean isExist = redisTemplate.hasKey(redisKey);
        if (Boolean.TRUE.equals(isExist)) {
            // 检验是否达到最大请求
            Long size = redisTemplate.opsForValue().increment(redisKey, 1);
            if (Objects.nonNull(size) && size > limitAnno.maxRequest()) {
                log.error("操作频繁，请稍后再试");
            }
        } else {
            redisTemplate.opsForValue().set(redisKey, 1, limitAnno.seconds(), TimeUnit.SECONDS);

            String script = "redis.call('SADD', KEYS[1], ARGV[1]);" +
                    "redis.call('EXPIRE', KEYS[1], ARGV[2]);";
            redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), List.of(redisKey), currentTimeMillis, limitAnno.seconds());

            /* redisTemplate.execute(new SessionCallback<>() {
                @Override
                public Object execute(@NotNull RedisOperations operations) {
                    operations.multi();
                    operations.opsForSet().add(redisKey, currentTimeMillis);
                    operations.expire(redisKey, limitAnno.seconds(), TimeUnit.SECONDS);
                    operations.exec();
                    return null;
                }
            });*/
        }
    }

    /**
     * 根据Token(用户id)接口防刷限制, set类型，保存的是请求接口的时间戳
     */
    public static final String getApiRequestLimitWithUserId(String uri, String userId) {
        return "api_request_limit:" + uri + ":" + userId;
    }

    /**
     * 根据请求的ip接口防刷限制, set类型，保存的是请求接口的时间戳
     */
    public static final String getApiRequestLimitWithIp(String uri, String ip) {
        return "api_request_limit:" + uri + ":" + ip;
    }

    /**
     * 根据token获取当前用户信息
     */
    public static String getUserId(HttpServletRequest request) {
        // 获取当前用户信息
        String userStr = request.getHeader(AuthConstant.DETAILS_USER_ID);
        if (StringUtils.isEmpty(userStr)) {
            log.error("暂未登录或token已经过期");
        }
        // 用户信息
        /* UserDto userDto = JSONUtil.toBean(userStr, UserDto.class);
        if (ObjectUtils.isEmpty(userDto)) {
            log.error("未知业务错误");
        }
        return userDto.getId();*/
        return "123456";
    }

}
